package netty.test100;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 *see  http://www.importnew.com/19816.html
 *
 *
 * 可以将一个对象或者更多信息附着到SelectionKey上，这样就能方便的识别某个给定的通道。
 * 例如，可以附加 与通道一起使用的Buffer，或是包含聚集数据的某个对象。使用方法如下：
 * selectionKey.attach(theObject);
 * Object attachedObj = selectionKey.attachment();
 *
 * 还可以在用register()方法向Selector注册Channel的时候附加对象。如：
 * SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
 */
public class NioServer {

    private static final int BUF_SIZE=1024;
    private static final int PORT = 8080;
    private static final int TIMEOUT = 3000;

    public static void main(String[] args)
    {
        Selector selector = null;
        ServerSocketChannel server = null;
        try{
            server= ServerSocketChannel.open();
            server.socket().bind(new InetSocketAddress(PORT));
            //1.与selector一起使用的时候 必须切换到非阻塞模式这意味着不能将FileChannel与Selector一起使用，
            // 因为FileChannel不能切换到非阻塞模式。而套接字通道都可以
            server.configureBlocking(false);
            selector = Selector.open();
            //2，当向Selector注册Channel时，register()方法会返回一个SelectionKey对象,这个对象包含了一些你感兴趣的属性：
            // （1）interest集合 （2）ready集合  （3）Channel （4）Selector 附加的对象（可选）
            // 当像Selector注册Channel时，Channel.register()方法会返回一个SelectionKey 对象。
            // 这个对象代表了注册到该Selector的通道。可以通过SelectionKey的selectedKeySet()
            // （注意和下面的selector.selectedKeys,一个表示注册事件，一个表示已准备好的事件）方法访问这些对象
            server.register(selector, SelectionKey.OP_ACCEPT);

            //3，向selector,注册了通道，和感兴趣的事件后，select 方法会返回 准备好的感兴趣事件，select()一直阻塞到
            //感兴趣事件的到达，select(time),阻塞一定时间，selectNow立即返回（没有回返回0）

            while(true){
                if(selector.select(TIMEOUT) == 0){//select 的返回结果表示有多少通道就绪了，0 表示没有，所以要再次select
                    continue;
                }
                //4,一旦调用了select()方法，并且返回值表明有一个或更多个通道就绪了，然后可以通过调用selector的selectedKeys()方法，
                // 访问“已选择键集（selected key set）”中的就绪通道
                Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                while(iter.hasNext()){
                    SelectionKey key = iter.next();
                    //5，可以通过is 开头的方法 单个判断 那个事件就绪了，也可以通过  int readySet = selectionKey.readyOps(); 方法
                    //一次读出所有的就绪事件
                    if(key.isAcceptable()){
                        handleAccept(key);
                    }
                    if(key.isReadable()){
                        handleRead(key);
                    }
                    if(key.isWritable() && key.isValid()){
                        handleWrite(key);
                    }
                    if(key.isConnectable()){
                        System.out.println("isConnectable = true");
                    }
                    //6,注意每次迭代末尾的keyIterator.remove()调用。Selector不会自己从已选择键集中移除SelectionKey实例。
                    // 必须在处理完通道时自己移除。下次该通道变成就绪时，Selector会再次将其放入已选择键集中
                    iter.remove();
                }
            }

        }catch(IOException e){
            e.printStackTrace();
        }finally{
            try{
                if(selector!=null){
                    selector.close();
                }
                if(server!=null){
                    server.close();
                }
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }

    public static void handleAccept(SelectionKey key) throws IOException{
        //从selectionKey 访问channel （Selector selector = selectionKey.selector(); 通过selectionKey 方法 selector）
        ServerSocketChannel ssChannel = (ServerSocketChannel)key.channel();
        SocketChannel sc = ssChannel.accept();
        sc.configureBlocking(false);
        sc.register(key.selector(), SelectionKey.OP_READ,ByteBuffer.allocateDirect(BUF_SIZE));
    }

    public static void handleRead(SelectionKey key) throws IOException{
        SocketChannel sc = (SocketChannel)key.channel();
        ByteBuffer buf = (ByteBuffer)key.attachment();
        long bytesRead = sc.read(buf);
        while(bytesRead>0){
            buf.flip();
            while(buf.hasRemaining()){
                System.out.print((char)buf.get());
            }
            System.out.println();
            buf.clear();
            bytesRead = sc.read(buf);
        }
        if(bytesRead == -1){
            sc.close();
        }
    }

    public static void handleWrite(SelectionKey key) throws IOException{
        ByteBuffer buf = (ByteBuffer)key.attachment();
        buf.flip();
        SocketChannel sc = (SocketChannel) key.channel();
        while(buf.hasRemaining()){
            sc.write(buf);
        }
        buf.compact();
    }
}
